package quantum2.math;

public final class Complex {

    public static final Complex I = rect(0, 1);
    public static final Complex ONE = rect(1, 0);

    private final double re;
    private final double im;

    private Complex(double re, double im) {
        this.re = re;
        this.im = im;
    }

    public static Complex number(double x) {
        return rect(x, 0);
    }

    public static Complex rect(double re, double im) {
        return new Complex(re, im);
    }

    public static Complex polar(double a, double phi) {
        return rect(a * Math.cos(phi), a * Math.sin(phi));
    }

    public double getRe() {
        return re;
    }

    public double getIm() {
        return im;
    }

    public Complex accum(Complex that) {
        return that == null ? this : add(that);
    }

    public Complex add(Complex that) {
        double re = this.getRe() + that.getRe();
        double im = this.getIm() + that.getIm();
        return rect(re, im);
    }

    public Complex sub(Complex that) {
        double re = this.getRe() - that.getRe();
        double im = this.getIm() - that.getIm();
        return rect(re, im);
    }

    public Complex mul(Complex that) {
        return rect(this.re * that.re - this.im * that.im, this.re * that.im + this.im * that.re);
    }

    public Complex div(Complex that) {
        double nomRe = this.re * that.re + this.im * that.im;
        double nomIm = -this.re * that.im + this.im * that.re;
        double denom = that.conj();
        return rect(nomRe / denom, nomIm / denom);
    }

    public Complex neg() {
        return rect(-getRe(), -getIm());
    }

    public Complex sqr() {
        return mul(this);
    }

    public double conj() {
        return re * re + im * im;
    }

    public String toString() {
        return getRe() + " + i" + getIm();
    }

    /**
     * [+|-]N[(+|-|+-)(iN|Ni)]
     */
    public static Complex parseComplex(String str) throws NumberFormatException {
        String origStr = str;
        str = str.trim();
        String sign1;
        if (str.startsWith("-") || str.startsWith("+")) {
            sign1 = str.substring(0, 1);
            str = str.substring(1).trim();
        } else {
            sign1 = null;
        }
        int p1 = str.indexOf('-');
        int p2 = str.indexOf('+');
        String num1;
        String sign21;
        String sign22;
        String num2;
        if (p1 >= 0 && p2 >= 0) {
            int p = Math.min(p1, p2);
            num1 = str.substring(0, p).trim();
            sign21 = str.substring(p, p + 1);
            String rest = str.substring(p + 1).trim();
            if (rest.startsWith("+") || rest.startsWith("-")) {
                sign22 = rest.substring(0, 1);
                num2 = rest.substring(1).trim();
            } else {
                sign22 = null;
                num2 = rest;
            }
        } else if (p1 >= 0) {
            num1 = str.substring(0, p1).trim();
            sign21 = str.substring(p1, p1 + 1);
            sign22 = null;
            num2 = str.substring(p1 + 1).trim();
        } else if (p2 >= 0) {
            num1 = str.substring(0, p2);
            sign21 = str.substring(p2, p2 + 1).trim();
            sign22 = null;
            num2 = str.substring(p2 + 1).trim();
        } else {
            num1 = str;
            sign21 = null;
            sign22 = null;
            num2 = null;
        }
        boolean imaginary = false;
        if (num1.toLowerCase().startsWith("i")) {
            num1 = num1.substring(1).trim();
            imaginary = true;
        }
        if (num1.toLowerCase().endsWith("i")) {
            num1 = num1.substring(0, num1.length() - 1).trim();
            imaginary = true;
        }
        if (imaginary) {
            if (num2 != null)
                throw new NumberFormatException("Multiple imaginary parts in " + origStr);
            double re = sign(sign1) * (num1.isEmpty() ? 1 : Double.parseDouble(num1));
            return rect(0, re);
        } else {
            double re = sign(sign1) * Double.parseDouble(num1);
            double im;
            if (num2 != null) {
                if (num2.toLowerCase().startsWith("i")) {
                    num2 = num2.substring(1).trim();
                }
                if (num2.toLowerCase().endsWith("i")) {
                    num2 = num2.substring(0, num2.length() - 1).trim();
                }
                im = sign(sign21) * sign(sign22) * (num2.isEmpty() ? 1 : Double.parseDouble(num2));
            } else {
                im = 0;
            }
            return rect(re, im);
        }
    }

    private static double sign(String sign) {
        if ("-".equals(sign)) {
            return -1;
        } else {
            return 1;
        }
    }
}
